package uz.rt.units;

/**
 * The magnitude of length depends on its value and its unit. This class supports two units:
 * <ul>
 * <li>mm</li>
 * <li>cm</li>
 * </ul>
 * <p/>
 * Between these units conversions can be done using the scale function.
 */
public class Length implements Comparable<Length>
{
// ------------------------------ FIELDS ------------------------------

    /** Magnitude of the length */
    protected double value;
    /** Length unit */
    protected Unit unit;

// --------------------------- CONSTRUCTORS ---------------------------

    /**
     * Copy constructor
     *
     * @param length Length instance
     */
    public Length(final Length length)
    {
        setValue(length.getValue());
        setUnit(length.getUnit());
    }

    /**
     * Constructor initialises the class and sets the magnitude and unit of the length.
     *
     * @param value magnitude of the length
     * @param unit  length unit
     */
    public Length(final double value, Unit unit)
    {
        setValue(value);
        setUnit(unit);
    }

    /**
     * Set the magnitude of the length
     *
     * @param value Magnitude of the length
     */
    public void setValue(double value)
    {
        if (value >= 0)
            this.value = value;
    }

// --------------------- GETTER / SETTER METHODS ---------------------

    /**
     * Get the length unit
     *
     * @return Length unit
     */
    public Unit getUnit()
    {
        return unit;
    }

    /**
     * Set the length unit
     *
     * @param unit Length unit
     */
    public void setUnit(Unit unit)
    {
        this.unit = unit;
    }

    /**
     * Get the magnitude of the length
     *
     * @return Magnitude of the length
     */
    public double getValue()
    {
        return value;
    }

// ------------------------ CANONICAL METHODS ------------------------

    @Override
    public boolean equals(Object o)
    {
        if (this == o) return true;
        if (!(o instanceof Length)) return false;

        Length length = (Length) o;
        Length t = new Length(length);
        t.scale(unit);
        double d = t.getValue();
        return (Double.compare(value, d) == 0);
    }

    @Override
    public int hashCode()
    {
        int result;
        long temp;
        temp = value != +0.0d ? Double.doubleToLongBits(value) : 0L;
        result = (int) (temp ^ (temp >>> 32));
        result = 31 * result + (unit != null ? unit.hashCode() : 0);
        return result;
    }

    @Override
    public String toString()
    {
        String v = String.valueOf(value) + " ";
        if (unit == Unit.cm) return v + "cm";
        if (unit == Unit.mm) return v + "mm";
        return "";
    }

// ------------------------ INTERFACE METHODS ------------------------


// --------------------- Interface Comparable ---------------------

    public int compareTo(Length o)
    {
        return Double.compare(this.scaled(Unit.mm), o.scaled(Unit.mm));
    }

// -------------------------- OTHER METHODS --------------------------

    /**
     * Length value scaled to a given unit.
     *
     * @param unit Length unit
     * @return Scaled length value
     */
    public double scaled(Unit unit)
    {
        Length l = new Length(value, this.unit);
        l.scale(unit);
        return l.getValue();
    }

    /**
     * Scale the magnitude of the length based on a unit
     * Current support conversion are:
     * <ul>
     * <li>mm to cm</li>
     * <li>cm to mm</li>
     * </ul>
     *
     * @param unit Length unit
     */
    public void scale(Unit unit)
    {
        switch (unit)
        {
            case mm:
                if (this.unit == Unit.cm) value = cm2mm(value);
                this.unit = unit;
                break;
            case cm:
                if (this.unit == Unit.mm) value = mm2cm(value);
                this.unit = unit;
                break;
            default:
                break;
        }
    }

    /**
     * Convert centimeter to millimeter
     *
     * @param x length value
     * @return Converted length value
     */
    public double cm2mm(final double x)
    {
        return x * 10;
    }

    /**
     * Convert millimeter to centimeter
     *
     * @param x length value
     * @return Converted length value
     */
    public double mm2cm(final double x)
    {
        return x / 10;
    }

    /**
     * Set the length unit based on a String value. The current supported length units in String format are: cm and mm.
     *
     * @param unit Length unit in String format
     */
    public void setUnit(String unit)
    {
        if (unit.equals("cm")) this.unit = Unit.cm;
        else if (unit.equals("mm")) this.unit = Unit.mm;
        else throw new RuntimeException(
                    "Trying to set an unknown length unit. Current supported length units are: cm and mm.");
    }

// -------------------------- ENUMERATIONS --------------------------

    /** Defines the different length units supported by this class */
    public enum Unit
    {
        mm, cm
    }
}
